package com.reactim.message.security;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 连接访问控制组件
 * 提供用户连接的权限控制和访问管理功能
 */
@Component
@Slf4j
@RequiredArgsConstructor
public class ConnectionAccessControl {
    
    private final ReactiveStringRedisTemplate redisTemplate;
    
    // 本地缓存的用户权限信息
    private final ConcurrentHashMap<Long, UserPermission> userPermissionCache = new ConcurrentHashMap<>();
    
    // 被禁用的用户集合
    private final Set<Long> bannedUsers = ConcurrentHashMap.newKeySet();
    
    @Value("${connection.security.access-control.max-connections-per-user:5}")
    private int maxConnectionsPerUser;
    
    @Value("${connection.security.access-control.enable-permission-check:true}")
    private boolean enablePermissionCheck;
    
    @Value("${connection.security.access-control.cache-ttl-minutes:30}")
    private int cacheTtlMinutes;
    
    // Redis键前缀
    private static final String USER_CONNECTIONS_COUNT_KEY = "user:connections:count:";
    private static final String USER_PERMISSION_KEY = "user:permission:";
    private static final String BANNED_USERS_KEY = "banned:users";
    
    /**
     * 检查用户是否有连接权限
     * 
     * @param userId 用户ID
     * @return 权限检查结果
     */
    public Mono<AccessResult> checkConnectionPermission(Long userId) {
        return Mono.fromCallable(() -> {
            // 1. 检查用户是否被禁用
            if (isUserBanned(userId)) {
                log.warn("被禁用用户尝试连接: userId={}", userId);
                return AccessResult.denied("用户已被禁用");
            }
            
            return AccessResult.allowed();
        })
        .flatMap(result -> {
            if (!result.isAllowed()) {
                return Mono.just(result);
            }
            
            // 2. 检查用户连接数限制
            return checkUserConnectionLimit(userId);
        })
        .flatMap(result -> {
            if (!result.isAllowed() || !enablePermissionCheck) {
                return Mono.just(result);
            }
            
            // 3. 检查用户权限
            return checkUserPermission(userId);
        });
    }
    
    /**
     * 检查用户连接数限制
     */
    private Mono<AccessResult> checkUserConnectionLimit(Long userId) {
        String countKey = USER_CONNECTIONS_COUNT_KEY + userId;
        
        return redisTemplate.opsForValue().get(countKey)
                .map(countStr -> {
                    int currentConnections = Integer.parseInt(countStr);
                    if (currentConnections >= maxConnectionsPerUser) {
                        log.warn("用户连接数超限: userId={}, current={}, limit={}", 
                                userId, currentConnections, maxConnectionsPerUser);
                        return AccessResult.denied("用户连接数已达上限");
                    }
                    return AccessResult.allowed();
                })
                .defaultIfEmpty(AccessResult.allowed())
                .onErrorReturn(AccessResult.denied("检查连接数限制时发生错误"));
    }
    
    /**
     * 检查用户权限
     */
    private Mono<AccessResult> checkUserPermission(Long userId) {
        // 先检查本地缓存
        UserPermission cachedPermission = userPermissionCache.get(userId);
        if (cachedPermission != null && !cachedPermission.isExpired()) {
            return Mono.just(cachedPermission.hasConnectionPermission() ? 
                    AccessResult.allowed() : AccessResult.denied("用户无连接权限"));
        }
        
        // 从Redis获取权限信息
        String permissionKey = USER_PERMISSION_KEY + userId;
        return redisTemplate.opsForValue().get(permissionKey)
                .map(permissionStr -> {
                    UserPermission permission = parsePermission(permissionStr);
                    // 更新本地缓存
                    userPermissionCache.put(userId, permission);
                    
                    return permission.hasConnectionPermission() ? 
                            AccessResult.allowed() : AccessResult.denied("用户无连接权限");
                })
                .defaultIfEmpty(AccessResult.allowed()) // 默认允许连接
                .onErrorReturn(AccessResult.denied("检查用户权限时发生错误"));
    }
    
    /**
     * 增加用户连接计数
     */
    public Mono<Boolean> incrementUserConnectionCount(Long userId) {
        String countKey = USER_CONNECTIONS_COUNT_KEY + userId;
        
        return redisTemplate.opsForValue().increment(countKey)
                .flatMap(count -> {
                    if (count == 1) {
                        // 设置过期时间
                        return redisTemplate.expire(countKey, Duration.ofHours(24))
                                .map(success -> true);
                    }
                    return Mono.just(true);
                })
                .doOnSuccess(success -> log.debug("用户连接计数增加: userId={}", userId))
                .onErrorReturn(false);
    }
    
    /**
     * 减少用户连接计数
     */
    public Mono<Boolean> decrementUserConnectionCount(Long userId) {
        String countKey = USER_CONNECTIONS_COUNT_KEY + userId;
        
        return redisTemplate.opsForValue().decrement(countKey)
                .flatMap(count -> {
                    if (count <= 0) {
                        // 删除计数键
                        return redisTemplate.delete(countKey).map(deleted -> true);
                    }
                    return Mono.just(true);
                })
                .doOnSuccess(success -> log.debug("用户连接计数减少: userId={}", userId))
                .onErrorReturn(false);
    }
    
    /**
     * 获取用户当前连接数
     */
    public Mono<Integer> getUserConnectionCount(Long userId) {
        String countKey = USER_CONNECTIONS_COUNT_KEY + userId;
        
        return redisTemplate.opsForValue().get(countKey)
                .map(Integer::parseInt)
                .defaultIfEmpty(0)
                .onErrorReturn(0);
    }
    
    /**
     * 禁用用户
     */
    public Mono<Boolean> banUser(Long userId, String reason) {
        bannedUsers.add(userId);
        
        return redisTemplate.opsForSet().add(BANNED_USERS_KEY, userId.toString())
                .map(added -> {
                    log.info("用户已被禁用: userId={}, reason={}", userId, reason);
                    return added > 0;
                })
                .onErrorReturn(false);
    }
    
    /**
     * 解禁用户
     */
    public Mono<Boolean> unbanUser(Long userId) {
        bannedUsers.remove(userId);
        
        return redisTemplate.opsForSet().remove(BANNED_USERS_KEY, userId.toString())
                .map(removed -> {
                    log.info("用户已解禁: userId={}", userId);
                    return removed > 0;
                })
                .onErrorReturn(false);
    }
    
    /**
     * 检查用户是否被禁用
     */
    private boolean isUserBanned(Long userId) {
        // 先检查本地缓存
        if (bannedUsers.contains(userId)) {
            return true;
        }
        
        // 从Redis检查（同步方式，用于快速检查）
        try {
            Boolean isMember = redisTemplate.opsForSet()
                    .isMember(BANNED_USERS_KEY, userId.toString())
                    .block(Duration.ofSeconds(1));
            
            if (Boolean.TRUE.equals(isMember)) {
                bannedUsers.add(userId); // 更新本地缓存
                return true;
            }
        } catch (Exception e) {
            log.warn("检查用户禁用状态时发生错误: userId={}, error={}", userId, e.getMessage());
        }
        
        return false;
    }
    
    /**
     * 设置用户权限
     */
    public Mono<Boolean> setUserPermission(Long userId, boolean hasConnectionPermission, 
                                          boolean hasMessagePermission) {
        String permissionKey = USER_PERMISSION_KEY + userId;
        String permissionValue = String.format("%b,%b,%d", 
                hasConnectionPermission, hasMessagePermission, System.currentTimeMillis());
        
        return redisTemplate.opsForValue().set(permissionKey, permissionValue, 
                        Duration.ofMinutes(cacheTtlMinutes))
                .doOnSuccess(success -> {
                    // 更新本地缓存
                    UserPermission permission = new UserPermission(hasConnectionPermission, 
                            hasMessagePermission, System.currentTimeMillis() + Duration.ofMinutes(cacheTtlMinutes).toMillis());
                    userPermissionCache.put(userId, permission);
                    
                    log.debug("用户权限已设置: userId={}, connection={}, message={}", 
                            userId, hasConnectionPermission, hasMessagePermission);
                })
                .onErrorReturn(false);
    }
    
    /**
     * 清理过期的缓存数据
     */
    public void cleanupExpiredCache() {
        long now = System.currentTimeMillis();
        
        // 清理过期的权限缓存
        userPermissionCache.entrySet().removeIf(entry -> entry.getValue().isExpired(now));
        
        log.debug("清理过期权限缓存完成");
    }
    
    /**
     * 解析权限字符串
     */
    private UserPermission parsePermission(String permissionStr) {
        try {
            String[] parts = permissionStr.split(",");
            boolean hasConnectionPermission = Boolean.parseBoolean(parts[0]);
            boolean hasMessagePermission = Boolean.parseBoolean(parts[1]);
            long expiryTime = Long.parseLong(parts[2]);
            
            return new UserPermission(hasConnectionPermission, hasMessagePermission, expiryTime);
        } catch (Exception e) {
            log.warn("解析权限字符串失败: {}", permissionStr);
            return new UserPermission(true, true, System.currentTimeMillis() + Duration.ofMinutes(cacheTtlMinutes).toMillis());
        }
    }
    
    /**
     * 用户权限信息
     */
    private static class UserPermission {
        private final boolean hasConnectionPermission;
        private final boolean hasMessagePermission;
        private final long expiryTime;
        
        public UserPermission(boolean hasConnectionPermission, boolean hasMessagePermission, long expiryTime) {
            this.hasConnectionPermission = hasConnectionPermission;
            this.hasMessagePermission = hasMessagePermission;
            this.expiryTime = expiryTime;
        }
        
        public boolean hasConnectionPermission() {
            return hasConnectionPermission;
        }
        
        public boolean hasMessagePermission() {
            return hasMessagePermission;
        }
        
        public boolean isExpired() {
            return isExpired(System.currentTimeMillis());
        }
        
        public boolean isExpired(long currentTime) {
            return currentTime > expiryTime;
        }
    }
    
    /**
     * 访问结果
     */
    public static class AccessResult {
        public static final AccessResult ALLOWED = new AccessResult(true, null);
        
        private final boolean allowed;
        private final String reason;
        
        private AccessResult(boolean allowed, String reason) {
            this.allowed = allowed;
            this.reason = reason;
        }
        
        public static AccessResult allowed() {
            return new AccessResult(true, null);
        }
        
        public static AccessResult denied(String reason) {
            return new AccessResult(false, reason);
        }
        
        public boolean isAllowed() {
            return allowed;
        }
        
        public String getReason() {
            return reason;
        }
    }
}